﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BizzCommInterfaces;
using System.Timers;
using log4net;

namespace BizzCore
{
    public class ProcessController
    {
        private static readonly ILog auditLog =
                      LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        private static ProcessController _instance = null;
        private bool _working {set; get;}
        private Timer bizzTimer;
        private double _interval;
        
        protected ProcessController()
        {
            _working = true;
            _interval = 30000;
            bizzTimer = new Timer(_interval);
            bizzTimer.Elapsed += new ElapsedEventHandler(bizzTimer_Elapsed);
        }


        public static ProcessController getInstance()
        {
            if (_instance == null)
                _instance = new ProcessController();
            return _instance;
        }

        private void startProcessing()
        {
            List<IInbox> inboxList = TraysHandler.getInstance().getConcreteInboxes();
            foreach (IInbox box in inboxList)
            {
                int msgCount = 0;
                try
                {
                    msgCount = box.getCount();
                }
                catch (Exception ex)
                {
                    auditLog.Error("Se produjo un error al buscar la cantidad de mensajes en la bandeja: " + box.getConcreteInboxName() + " " + ex.Message);
                }

                while (msgCount > 0) 
                {
                    HashSet<IOutbox> outboxes = new HashSet<IOutbox>();
                    string messageToRead = box.getWorkingMessage();
                    try
                    {
                        BizzMessage msg = box.readMessage(messageToRead);

                        List<Rule> rules = RulesHandler.getInstance().getRuleList();
                        List<Rule> rulesToLog = new List<Rule>();

                        foreach (var theRule in rules)
                        {
                            IOutbox outbox = null;
                            try
                            {
                                outbox = theRule.evaluateRule(msg);
                                // Se evaluo correctamente el mensaje --------------------------------------------
                            }
                            catch (Exception)
                            {
                                // Falló la evaluación del mensaje --------------------------------------------
                                auditLog.Info("Se produjo un error en la evaluación del mensaje: " + messageToRead);
                            }
                            //IOutbox outbox = theRule.evaluateRule(msg);
                            if (outbox != null)
                            {
                                outboxes.Add(outbox);
                                rulesToLog.Add(theRule);
                            }

                        }
                        if (outboxes.Count() > 0)
                        {
                            // El mensaje se escribirá en las bandejas
                            try
                            {
                                writeMessage(outboxes.ToList(), msg);
                                saveMessageToDB(box, outboxes.ToList(), msg, rulesToLog);

                            }
                            catch (Exception)
                            {
                                //Hubo error escribiendo el mensaje
                                auditLog.Info("Se produjo un error escribiendo el mensaje " + messageToRead + " en las bandejas de salida correspondientes.");
                            }
                        }
                        else
                        {
                            saveNotMatchedMessageToDB(box, msg);
                        }
                    }
                    catch (Exception ex)
                    {
                        //Hubo error leyendo el mensaje, el formato no es valido o algo asi
                        auditLog.Info("Se produjo un error leyendo el mensaje: " + messageToRead + ". Posiblemente el formato del mismo no es correcto.");
                    }
                    box.markAsRead();
                    msgCount = box.getCount();
                }
            }
        }

        public void writeMessage(List<IOutbox> outboxes, BizzMessage msg)
        {
            foreach (IOutbox box in outboxes)
            {
                box.writeMessage(msg);
            }
        }

        public void startServer()
        {
            bizzTimer.Start();
            //startProcessing();
        }

        public void stopServer()
        {
            bizzTimer.Stop();
        }

        public void setTimerInterval(double interval)
        {
            bizzTimer.Interval = interval*1000;
        }


        private void saveMessageToDB(IInbox inbox, List<IOutbox> outboxList, BizzMessage msg, List<Rule> rules)
        {
            using (BizzCommContext ctx = new BizzCommContext())
            {
                MessageEntity newMsg = new MessageEntity();

                newMsg.MsgDate = DateTime.Now;
                List<RuleMatchedEntity> ruleMatchedList = new List<RuleMatchedEntity>();
                foreach (Rule rule in rules)
                {
                    RuleMatchedEntity ruleMatched = new RuleMatchedEntity();
                    ruleMatched.Rule = rule.ToString();
                    ruleMatchedList.Add(ruleMatched);
                    
                }
                newMsg.RulesMatched = ruleMatchedList;

                newMsg.Inbox = inbox.ToString();
                newMsg.InboxType = inbox.getInboxName();

                List<OutboxEntity> outboxLogList = new List<OutboxEntity>();
                foreach (IOutbox outbox in outboxList)
                {
                    OutboxEntity outboxLog = new OutboxEntity();
                    outboxLog.OutboxName = outbox.ToString();
                    outboxLogList.Add(outboxLog);
                }
                newMsg.Ouboxes = outboxLogList;

                newMsg.Title = msg.Title;

                List<MessageAttributeEntity> msgAttribEntityList = new List<MessageAttributeEntity>();
                foreach (MsgAttribute attrib in msg.Attributes)
                {
                    MessageAttributeEntity msgAttribEntity = new MessageAttributeEntity();
                    msgAttribEntity.Name = attrib.Name;
                    msgAttribEntity.Type = attrib.Type.ToString();
                    msgAttribEntity.Value = attrib.Value;
                    msgAttribEntityList.Add(msgAttribEntity);
                }
                newMsg.Attributes = msgAttribEntityList;
                ctx.Messages.Add(newMsg);
                ctx.SaveChanges();
            }
        }


        private void saveNotMatchedMessageToDB(IInbox inbox, BizzMessage msg)
        {
            using (BizzCommContext ctx = new BizzCommContext())
            {
                MessageEntity newMsg = new MessageEntity();
                newMsg.MsgDate = DateTime.Now;
                newMsg.Inbox = inbox.ToString();
                newMsg.InboxType = inbox.getInboxName();
                newMsg.Title = msg.Title;

                List<MessageAttributeEntity> msgAttribEntityList = new List<MessageAttributeEntity>();
                foreach (MsgAttribute attrib in msg.Attributes)
                {
                    MessageAttributeEntity msgAttribEntity = new MessageAttributeEntity();
                    msgAttribEntity.Name = attrib.Name;
                    msgAttribEntity.Type = attrib.Type.ToString();
                    msgAttribEntity.Value = attrib.Value;
                    msgAttribEntityList.Add(msgAttribEntity);
                }
                newMsg.Attributes = msgAttribEntityList;
                ctx.Messages.Add(newMsg);
                ctx.SaveChanges();
            }
        }

        public List<MessageEntity> getMessages(int max)
        {
            List<MessageEntity> msgList = new List<MessageEntity>();
            using (BizzCommContext ctx = new BizzCommContext())
            {
                var msgs = (from m in ctx.Messages
                            orderby m.Id descending
                            select m).Take(max).ToList();
                msgList = msgs;
            }
            return msgList;
        }

        public List<MessageAttributeEntity> getMesageAttributes(int msgId)
        {
            List<MessageAttributeEntity> attributeList = new List<MessageAttributeEntity>();
            using (BizzCommContext ctx = new BizzCommContext())
            {
                var msgEntity = (from a in ctx.Messages
                                  where a.Id == msgId
                                  select a).Single();
                attributeList = msgEntity.Attributes;
             }
            return attributeList;
        }

        public List<RuleMatchedEntity> getMessageRules(int msgId)
        {
            List<RuleMatchedEntity> rules = new List<RuleMatchedEntity>();
            using (BizzCommContext ctx = new BizzCommContext())
            {
                var msgRules = (from m in ctx.Messages
                                where m.Id == msgId
                                select m).Single();
                rules = msgRules.RulesMatched;
            }
            return rules;
        }

        public List<OutboxEntity> getMessageOutBoxes(int msgId)
        {
            List<OutboxEntity> outboxes = new List<OutboxEntity>();
            using (BizzCommContext ctx = new BizzCommContext())
            {
                var msgOutboxes = (from m in ctx.Messages
                                   where m.Id == msgId
                                   select m).Single();
                outboxes = msgOutboxes.Ouboxes;
            }
            return outboxes;
        }


        public List<MessageEntity> getMessages(List<string[]> filterAttrib, DateTime dateFrom, DateTime dateTo, string inbox)
        {
            
            List<MessageEntity> msgList = new List<MessageEntity>();

            using (BizzCommContext ctx = new BizzCommContext())
            {
                List<int> msgIds = ctx.Database.SqlQuery<int>(buildSQLQuery(filterAttrib,inbox)).ToList();
                var msgFiltered = (from m in ctx.Messages
                                    where msgIds.Contains(m.Id) && m.MsgDate >= dateFrom && m.MsgDate <= dateTo
                                    select m).ToList();
                
                msgList = msgFiltered;            
                
            }
            return msgList;
        }

        private string buildSQLQuery(List<string[]> filterAttrib, string inbox)
        {
            
            string sqlQuery = "select distinct m.Id from dbo.MessageAttribute a, Messages m where a.MessageEntity_Id = m.Id and " +
                               "  m.Inbox like '%" + inbox + "%' and ";
            if (filterAttrib.Count > 0)
            {
                string[] lastFilter = filterAttrib.Last();
                lastFilter[3] = "";
                foreach (string[] filter in filterAttrib)
                {
                    string condition = " exists (select 1 from MessageAttribute mi where Name = '" + filter[0] + "' and Value " + filter[1] + " '" + filter[2] +
                        "' and mi.MessageEntity_Id = m.Id) " + filter[3];


                    sqlQuery += condition;
                }
            }
            else
            {
                sqlQuery += " 1=1 ";
            }

            return sqlQuery;
        }


        #region Events
        void bizzTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            startProcessing();
        }
        #endregion

    }
}
